﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataProcessing
{
    /// <summary>
    /// 
    /// </summary>
    public class FastCalculation
    {
        public const int CACHE_RESOLUTION = 65536*2;
        private static DataProcessing.CalculationCache[][] _cache;
        private class Fragment
        {
            public uint cacheIndex=0;
            public uint accumulation=0;
            public uint remainder = 0;
        }

        public static ChromePairData.AlignMode Align
        {
            get;
            set;
        }


        /// <summary>
        /// 必须在第一次初始化之后调用。否则ChromePairData.AllWindowMax还没计算出来
        /// </summary>
        static public void Init()
        {
            
            _cache = new CalculationCache[ChromePairData.Data.Length][];
            for(int j=0;j<ChromePairData.Data.Length;j++)
            {
                _cache[j] = new CalculationCache[CACHE_RESOLUTION];
            }


            Parallel.For(0,ChromePairData.Data.Length,i=>
                {
                    Prepare(ChromePairData.BoundWindow, _cache[i], ChromePairData.Data[i].ChromeSampling,CACHE_RESOLUTION);
                }
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="observingWindow"></param>
        /// <param name="aCache"></param>
        /// <param name="samplings"></param>
        /// <returns></returns>
        static private uint Prepare(ValueRange observingWindow, CalculationCache[] aCache, List<ChrSampling> samplings,int pointPerWindow)
        {
            uint cacheWindowSize=InitCache(observingWindow, aCache, pointPerWindow);

            int i;
            //2. 将每个sample数据都计算其对每个window做的贡献，如果做了贡献，加入那个cache。
            //注意，一个sample有可能同时落在两个（多个）window.
            for (i = 0; i < samplings.Count; i++)
            {

                //2.1 对某个sample数据，计算出起始窗口和终止窗口
                int startWindow = 0;


                //todo:可以优化
                if (!observingWindow.Contains(samplings[i].Window.Start))
                {
                    continue;
                }
                startWindow = (int)((samplings[i].Begin - observingWindow.Start) / cacheWindowSize);


                //2.2.先对起始数据进行处理。
                //加入窗口
                aCache[startWindow].AddSample(samplings[i]);


                if (samplings[i].Window.End <= aCache[startWindow].Window.End)
                {
                    continue;

                }
                //2.3如果跨窗口了把他加入其他窗口
                int endWindow = (int)((samplings[i].End - observingWindow.Start) / cacheWindowSize);
                //如果是在放大模式下，endwindow有可能超出当前的观察窗口。所以要裁剪
                endWindow = Math.Min(endWindow, pointPerWindow - 1);
                for (int j = startWindow + 1; j <= endWindow; j++)
                {
                    aCache[j].AddSample(samplings[i]);
                }
            }
            return cacheWindowSize;
        }

        private static uint InitCache(ValueRange observingWindow, CalculationCache[] aCache, int pointPerWindow)
        {
            //1. 根据window和resolution求出cache个数=window.Size/resolution+1?和其对应的window
            int cacheCount = pointPerWindow;

            uint cacheWindowSize = 0;
            cacheWindowSize = GetCacheWindowSize(observingWindow, pointPerWindow);

            int i = 0;
            CalculationCache cache;

            for (i = 0; i < cacheCount; i++)
            {
                cache = new CalculationCache();
                cache.Window.Start = (uint)(observingWindow.Start + i * cacheWindowSize);
                cache.Window.End = cache.Window.Start + cacheWindowSize - 1;
                aCache[i] = cache;
            }

            return cacheWindowSize;
        }

        


        private static uint GetCacheWindowSize(ValueRange observingWindow, int pointPerWindow)
        {
            return (uint)((observingWindow.Size % pointPerWindow == 0) ? observingWindow.Size / pointPerWindow : observingWindow.Size / pointPerWindow + 1);
        }


        /// <summary>
        ///  关键代码，利用当前的cache来计算给定的窗口下的cache
        ///   //todo:alignpoint
        /// </summary>
        /// <param name="chromeIndex">第几对染色体</param>
        /// <param name="observingWindow">当前观察窗口</param>
        /// <param name="resolution">窗口的分辨率</param>
        /// <param name="needAlignmentTranslate">是否需要根据alignment的变换</param>
        public static  CalculationCache[]  ComputeCache(uint chromeIndex,
            ValueRange observingWindow,
            int resolution,
            bool needAlignmentTranslate)
        {
            CalculationCache[] cache = new CalculationCache[resolution];


            //1.init cache window value range
            InitCacheForChrome(observingWindow, cache, resolution,chromeIndex,needAlignmentTranslate);
            //计算小窗口
            if (cache[cache.Length-1].Window.Size < GetLocalWindowSize())
            {
                GetAccumulation_SmallWindow(chromeIndex, cache);
                return cache;
            }
            ///上一个cache计算的结果，对于下一次计算大有用处
            Fragment last = null;
            //2.对所有的cache，计算其accumulation
            foreach (CalculationCache aCache in cache)
            {
                if (aCache.Disable)
                {
                    aCache.Accumulation = 0;
                    continue;
                }
                last= CalculateAccumulation(chromeIndex,aCache,last);
            }
            return cache;
        }


        static private uint InitCacheForChrome(ValueRange observingWindow,
            CalculationCache[] aCache, 
            int resolution,
            uint chromeIndex,
            bool needAlignmentTranslate)
        {
            //1. 根据window和resolution求出cache个数=window.Size/resolution+1?和其对应的window
            int cacheCount = resolution;

            uint cacheWindowSize = 0;
            cacheWindowSize = GetCacheWindowSize(observingWindow, resolution);

            int i = 0;
            CalculationCache cache;

            for (i = 0; i < cacheCount; i++)
            {
                cache = new CalculationCache();
                cache.Window.Start = (uint)(observingWindow.Start + i * cacheWindowSize);
                cache.Window.End = cache.Window.Start + cacheWindowSize - 1;
                if (needAlignmentTranslate)
                {
                    cache.Window = ChromePairData.Data[chromeIndex].GetActualWindow(cache.Window);
                }
                if(cache.Window.OverlappedLength(ChromePairData.BoundWindow)==0) 
                {
                    cache.Disable = true;
                }
                aCache[i] = cache;
            }

            return cacheWindowSize;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="chromeIndex"></param>
        /// <param name="cache"></param>
        private static void GetAccumulation_SmallWindow(uint chromeIndex, CalculationCache[] cache)
        {
            List<ChrSampling> samplingList = new List<ChrSampling>();
            
            //对于每一个小窗口
            for (int i = 0; i < cache.Length; i++)
            {
                if (cache[i].Disable)
                {
                    cache[i].Accumulation = 0;
                    continue;
                }
                //1.看他是否跨local窗口
                Fragment startIndex = GetWindowContainsPoint(cache[i].Window.Start);
                Fragment endIndex = GetWindowContainsPoint(cache[i].Window.End);
                CalculateAccumulationForSmallWindow(chromeIndex, cache[i], startIndex, endIndex);
                
            }
        }

        /// <summary>
        ///计算某cache的accumulation，但是并不计算有多少chromesapling fall in。
        ///因为这会比较慢，达不到我们的主要目的：加速
        /// </summary>
        /// <param name="cache">
        /// cache to prepare
        /// </param>
        /// <param name="last">
        /// last==null:no last result
        ///
        /// </param>
        /// <returns></returns>
        private static Fragment CalculateAccumulation(uint chromeIndex, CalculationCache cache, Fragment last)
        {
            
            //1.cache window的起始,终止零碎片段计算

            //1.1起止窗口
            Fragment startIndex = GetWindowContainsPoint(cache.Window.Start);
            Fragment endIndex =GetWindowContainsPoint(cache.Window.End);

            //small window. 因为采用了alignment的截断之后会有如此情况
            if (cache.Window.Size < GetLocalWindowSize())
            {
                CalculateAccumulationForSmallWindow(chromeIndex, cache, startIndex, endIndex);
                return null;
            }


            //1。2起止窗口是否碎片？accumulation？
            Fragment startFragment = CalculateStartFragment_Accumulation(chromeIndex, startIndex, last);
            Fragment endFragment = CalculateEndFragment_Accumulation(chromeIndex, endIndex);

            uint completeWindowStart =0;
            if(startFragment==null)
            {
                completeWindowStart = startIndex.cacheIndex;
            }
            else
            {
                completeWindowStart = startIndex.cacheIndex+1;
                cache.Accumulation += startFragment.accumulation;
            }

            uint completeWindowEnd = 0;
            if (endFragment == null)
            {
                completeWindowEnd = endIndex.cacheIndex;
              
            }
            else
            {
                completeWindowEnd = endIndex.cacheIndex - 1;
                cache.Accumulation += endFragment.accumulation;
            }
                
            //2.对于包含的整个cache，直接将其accumulation累加进来
            uint i=0;
            for (i = completeWindowStart; i <=completeWindowEnd; i++)
            {
                cache.Accumulation += _cache[chromeIndex][i].Accumulation;
            }
            if (cache.Accumulation > uint.MaxValue / 2)
            {
                int a=0;
                a++;
            }
            //4.return endfragment for next time calculation
            return endFragment;
        }

        private static void CalculateAccumulationForSmallWindow(uint chromeIndex, CalculationCache cache, Fragment startIndex, Fragment endIndex)
        {
            CalculationCache[] chromeCache = _cache[chromeIndex];
            CalculationCache startCache = chromeCache[startIndex.cacheIndex];
            cache.Accumulation += CalculateSubWindowAccumulation(startCache, startCache.Window.Intersect(cache.Window));
            //2如果跨，对两个窗口内的部分都用其上的sampling计算
            if (startIndex != endIndex)
            {
                CalculationCache endCache = chromeCache[endIndex.cacheIndex];
                cache.Accumulation += CalculateSubWindowAccumulation(endCache, endCache.Window.Intersect(cache.Window));

            }
        }
        static uint GetLocalWindowSize()
        {
            return _cache[0][0].Window.Size;
        }
        /// <summary>
        /// 返回某点落于那个窗口的哪个位置
        /// </summary>
        /// <param name="pt"></param>
        /// <returns></returns>
        private static Fragment GetWindowContainsPoint(uint pt)
        {
            Fragment ret = new Fragment();
            
            ret.cacheIndex = (pt/ GetLocalWindowSize());
            ret.remainder = pt % GetLocalWindowSize();

            //因为如果是center justify的话，有可能出现窗口超出最大限制
            if (ret.cacheIndex >= CACHE_RESOLUTION)
            {
                ret.cacheIndex = CACHE_RESOLUTION-1;
                ret.remainder = 0;
            }
            return ret;

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="chromeIndex"></param>
        /// <param name="startFragment"></param>
        /// <param name="lastFragment"></param>
        /// <returns></returns>
        private static Fragment CalculateStartFragment_Accumulation(uint chromeIndex,Fragment startFragment,Fragment lastFragment)
        {
            //not a fragment,正好和某_cache起点重合
            if (startFragment.remainder == 1)
            {
                return null;
            }
            CalculationCache startCache = _cache[chromeIndex][startFragment.cacheIndex];
            if (lastFragment != null)
            {
                startFragment.accumulation = startCache.Accumulation - lastFragment.accumulation;
            }
            else
            {
                startFragment.accumulation= CalculateSubWindowAccumulation(startCache, new ValueRange { Start = startFragment.remainder+startCache.Window.Start-1, End = startCache.Window.End});
            }
            return startFragment;
        }

        private static Fragment CalculateEndFragment_Accumulation(uint chromeIndex, Fragment endFragment)
        {
            
            //not a fragment,恰好是某个窗口的最后一个点
            if (endFragment.remainder+1 == GetLocalWindowSize())
            {
                return null;
            }
            CalculationCache endCache= _cache[chromeIndex][endFragment.cacheIndex];
            endFragment.accumulation= CalculateSubWindowAccumulation(endCache,new ValueRange{Start=endCache.Window.Start,End=endFragment.remainder+endCache.Window.Start-1});
            return endFragment;
        }


        /// <summary>
        /// 计算父窗口的子窗口上的accumulation
        /// 这样做主要是可以减少需要遍历的sampling，落入子窗口的sampling，必在落入父窗口sampling中
        /// </summary>
        /// <param name="parent">父窗口</param>
        /// <param name="window">子窗口</param>
        /// <returns></returns>
        private static uint CalculateSubWindowAccumulation(CalculationCache parent,ValueRange window)
        {
            uint ret=0;
            foreach (ChrSampling sampling in parent.SamplesFallingIn)
            {
                ret += window.OverlappedLength(sampling.Window);
            }
            return ret;
        }
  
    }
}
